package cn.wj.school.cloud.aspect;


import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * @Author: yueding
 * @Description: AutoLogAspectJ
 * @Date: Created in 15:41 2018/4/5
 * @Modified By: 日志切面 Copyright(c) cai-inc.com
 */
@Aspect
@Slf4j
public class AutoLogAspectJ {

    @Pointcut("@within(cn.wj.school.cloud.aspect.JteachAutoLog) && execution(public * *(..))")
    public void AutoLogAspectClass() {}

    @Pointcut("@annotation(cn.wj.school.cloud.aspect.JteachAutoLog)")
    public void AutoLogAspectMethod() {}

    @Pointcut("@annotation(cn.wj.school.cloud.aspect.JteachAutoLog)")
    public void OperationLogAspectMethod() {

    }
    /**
     * Log format: [Module][service]start functionName. parameters.
     */
    @Before(value = "AutoLogAspectClass() || AutoLogAspectMethod()")
    public void doServiceBefore(final JoinPoint point) {
        logBefore(point);
    }

    /**
     * Log format: [Module][service]finish functionName. return values.
     */
    @AfterReturning(returning = "rtObj", value = "AutoLogAspectClass() || AutoLogAspectMethod() || OperationLogAspectMethod()")
    public void doServiceAfter(final JoinPoint point, final Object rtObj) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        /* 方法上有SkipAutoLog注解,不打log */
        if (isSkipAutoLogWork(method)) {
            return;
        }
        logAfter(point, rtObj, method);
        JteachAutoLog JteachAutoLog = method.getAnnotation(JteachAutoLog.class);

        if (null == JteachAutoLog) {
            /* 方法或类上都没有AutoLog注解,不打log */
            return;
        }
    }

    /**
     * Log format: [Module][service]functionName. throw exception: detail.
     */
    @AfterThrowing(throwing = "ex", value = "AutoLogAspectClass() || AutoLogAspectMethod() || OperationLogAspectMethod()")
    public void logServiceAfterThrowing(final JoinPoint point, Exception ex) {

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        logAfterThrowing(point, ex, method);
        /* 优先使用方法上的注解 */
        JteachAutoLog JteachAutoLog = method.getAnnotation(JteachAutoLog.class);
        if (null == JteachAutoLog) {
            /* 方法或类上都没有AutoLog注解,不打log */
            return;
        }
    }

    private void logBefore(final JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        /* 方法上有SkipAutoLog注解,不打log */
        if (isSkipAutoLogWork(method)) {
            return;
        }

        /* 优先使用方法上的注解 */
        StringBuilder sb = getLogInfoStr(point);
        if (sb == null) return;

        /* 打上方法名 */
        sb.append("start ");
        sb.append(method.getName());
        sb.append(". ");

        /* 打上入参 */
        Object[] args = point.getArgs();
        if ((null != args) && (args.length > 0)) {
            int count = args.length;
            boolean bSkipDot = true;
            for (int i = 0; i < count; i++) {
                Object arg = args[i];
                if (null != arg) {
                    if (bSkipDot) {
                        bSkipDot = false;
                    } else {
                        sb.append(",");
                    }
                    sb.append(arg.getClass().getSimpleName());
                    sb.append(":");
                    sb.append(arg.toString());
                }
            }

            sb.append(".");
        }

        log.info(sb.toString());
    }

    private StringBuilder getLogInfoStr(JoinPoint point) {
        JteachAutoLog JteachAutoLog = getAutoLogAnno(point);
        if (null == JteachAutoLog) {
            /* 方法或类上都没有AutoLog注解,不打log */
            return null;
        }

        StringBuilder sb = new StringBuilder();

        /* 按注解参数拼装log */
        appendAnnoParams(JteachAutoLog, sb);
        return sb;
    }

    private void logAfter(final JoinPoint point, final Object rtObj, Method method) {

        /* 优先使用方法上的注解 */
        JteachAutoLog JteachAutoLog = getAutoLogAnno(point);
        if (null == JteachAutoLog) {
            /* 方法或类上都没有AutoLog注解,不打log */
            return;
        }

        StringBuilder sb = new StringBuilder();

        /* 按注解参数拼装log */
        appendAnnoParams(JteachAutoLog, sb);

        /* 打上方法名 */
        sb.append("finish ");
        sb.append(method.getName());
        sb.append(". return: ");

        if (null != rtObj) {
            /* 打上返回值 */
            sb.append(rtObj.toString());

            sb.append(".");
        }

        log.info(sb.toString());
    }

    private void logAfterThrowing(final JoinPoint point, final Exception ex, Method method) {
        /* 方法上有SkipAutoLog注解,不打log */
        if (isSkipAutoLogWork(method)) {
            return;
        }

        /* 优先使用方法上的注解 */
        JteachAutoLog JteachAutoLog = getAutoLogAnno(point);
        if (null == JteachAutoLog) {
            /* 方法或类上都没有AutoLog注解,不打log */
            return;
        }

        StringBuilder sb = new StringBuilder();

        /* 按注解参数拼装log */
        appendAnnoParams(JteachAutoLog, sb);

        /* 打上方法名 */
        sb.append(method.getName());

        if (ex instanceof SkipLogException) {
            sb.append(". throw self design exception: ");
            sb.append("message: ");
            sb.append(Strings.isNullOrEmpty(ex.getMessage()) ? "" : ex.getMessage());
            sb.append(".");

            log.info(sb.toString());
        } else {

            sb.append(". throw exception: ");
            sb.append("message: ");
            sb.append(Strings.isNullOrEmpty(ex.getMessage()) ? "" : ex.getMessage());
            sb.append(". stacktrace: ");

            /* 打上异常调用栈 */
            sb.append(Throwables.getStackTraceAsString(ex));

            sb.append(".");
            log.error(sb.toString());
        }
    }

    private boolean isSkipAutoLogWork(Method method) {
        JteachAutoLogSkip JteachAutoLogSkip = method.getAnnotation(JteachAutoLogSkip.class);
        if (null != JteachAutoLogSkip) {
            return true;
        } else {
            return false;
        }
    }

    private JteachAutoLog getAutoLogAnno(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        /* 优先使用方法上的注解 */
        JteachAutoLog JteachAutoLog = method.getAnnotation(JteachAutoLog.class);
        if (null == JteachAutoLog) {
            /* 类上的注解次之 */
            JteachAutoLog = point.getTarget().getClass().getAnnotation(JteachAutoLog.class);
        }

        return JteachAutoLog;
    }

    private void appendAnnoParams(JteachAutoLog jteachAutoLog, StringBuilder sb) {
        if (!Strings.isNullOrEmpty(jteachAutoLog.module())) {
            sb.append("[");
            sb.append(jteachAutoLog.module());
            sb.append("]");
        }

        if (!Strings.isNullOrEmpty(jteachAutoLog.tag())) {
            sb.append("[");
            sb.append(jteachAutoLog.tag());
            sb.append("]");
        }
    }
}
